////////////////////////////////////////////////////////////////////////////////////
// $Revision: 38654 $
////////////////////////////////////////////////////////////////////////////////////
// Copyright 2010 SlickEdit Inc. 
// You may modify, copy, and distribute the Slick-C Code (modified or unmodified) 
// only if all of the following conditions are met: 
//   (1) You do not include the Slick-C Code in any product or application 
//       designed to run independently of SlickEdit software programs; 
//   (2) You do not use the SlickEdit name, logos or other SlickEdit 
//       trademarks to market Your application; 
//   (3) You provide a copy of this license with the Slick-C Code; and 
//   (4) You agree to indemnify, hold harmless and defend SlickEdit from and 
//       against any loss, damage, claims or lawsuits, including attorney's fees, 
//       that arise or result from the use or distribution of Your application.
////////////////////////////////////////////////////////////////////////////////////
#pragma option(pedantic,on)
#region Imports
#include "slick.sh"
#include "project.sh"
#include "xml.sh"
#import "compile.e"
#import "files.e"
#import "makefile.e"
#import "projconv.e"
#import "saveload.e"
#import "stdcmds.e"
#import "stdprocs.e"
#import "wkspace.e"
#endregion


#define MAKEFILE_BANNER          "# SlickEdit generated file.  Do not edit this file except in designated areas."
#define MAKEFILE_USERMOD_BEGIN   "# -----Begin user-editable area-----"
#define MAKEFILE_USERMOD_END     "# -----End user-editable area-----"
#define MAKEFILE_EXTENSION       ".mak"
#define MAKEFILE_GENERATION_MSG  "Generating makefile for project '%s1'..."
#define MAKEFILE_DEFAULT_FILE    "%rp%rn.mak"

#define MAKEFILE_FORMAT_GNUMAKE  1
#define MAKEFILE_FORMAT_NMAKE    2
#define MAKEFILE_FILESEP_GNUMAKE "/"
#define MAKEFILE_FILESEP_NMAKE   "\\"

#define SOURCE_EXTENSION_LIST    "*.c;*.cc;*.cp;*.cpp;*.cxx;*.c++;*.C;*.s;*.asm;*.masm"




struct COMPILE_TOOL_INFO
{
   _str     cmdLine;
   _str     variableName;
   _str     inputExt;
   _str     outputExt;
   boolean  linkObject; // add files of this type to link object list
   _str     chainedOutputExt; // final output ext after all rules are chained
   boolean  chainedLinkObject;
   boolean  ignore; // true for headers and other files that should not be included in the makefile
};

#if __UNIX__
// default to resolving dependent project symlinks during makefile generation
int def_resolve_dependency_symlinks=1;
#else
int def_resolve_dependency_symlinks=0;
#endif


/**
 * Generate a makefile from the specified project
 *
 * @param projectName
 *               Absolute filename of the project to build a makefile for
 * @param makefileName
 *               Filename to use for the makefile.  This should be relative
 *               to the project directory
 * @param openAfterGen
 *               Open the makefile for editing after generation
 * @param promptForInfo
 *               Prompt the user for information about the makefile
 * @param replaceEnvVars
 *               Replace %(ENVVAR) with its value
 *
 *               T - Replace %(ENVVAR) with value
 *               F - Replace %(ENVVAR) with $(ENVVAR)
 *
 * @return 0 on success, <0 otherwise
 */
_command int generate_makefile(_str projectName = _project_name, _str makefileName = "",
                               boolean openAfterGen = true, boolean promptForInfo = false,
                               boolean replaceEnvVars = false, boolean quiet = false) name_info(','VSARG2_CMDLINE|VSARG2_REQUIRES_MDI)
{
   int status = 0;

   // note that the makefile is being generated
   mou_hour_glass(1);

   // open the project
   int projectHandle = _xmlcfg_open(projectName, status);
   if(status) return status;

   // load the configurations list
   int numConfigs = 0;
   _str configList[] = null;
   _str gnucConfigList[] = null;
   _str vcppConfigList[] = null;
   _ProjectGet_ConfigNames(projectHandle, configList);
   int i;
   for(i = 0; i < configList._length(); i++) {
      // if this config supports makefile generation, put it in the proper list
      switch(lowcase(_ProjectGet_Type(projectHandle, configList[i]))) {
         case "gnuc":
            gnucConfigList[gnucConfigList._length()] = configList[i];
            numConfigs++;
            break;

         case "vcpp":
            vcppConfigList[vcppConfigList._length()] = configList[i];
            numConfigs++;
            break;

         default:
            // config not supported for makefile generation
            break;
      }
   }

   // make sure there were configurations that support makefile generation
   if(numConfigs <= 0) {
      // notify user
      _message_box("The project '" projectName "' has no configurations that support makefile generation.");
      return 0;
   }

   // build the filename for the makefile.  if no makefile is specified, it will default
   // to projectname.mak.  this is done by passing %rp%rn.mak to _parse_project_command
   _str filename = makefileName;
   if(filename == "") {
      // check the project file for the makefile name
      filename = _ProjectGet_BuildMakeFile(projectHandle);

      // if still empty supply the default
      if(filename == "") {
         filename = MAKEFILE_DEFAULT_FILE;
      }
   }
   filename = _parse_project_command(filename, "", projectName, "");

   // prompt the user for the makefile name if necessary
   if(promptForInfo) {
      // get the makefilename relative to the project dir
      if(filename != "") {
         filename = _RelativeToProject(filename, projectName);
      }

      // clear the generating makefile message
      clear_message();
      mou_set_pointer(0); //mou_hour_glass(0);

      _str promptResult = show("-modal _textbox_form",
                               "Enter the name for the makefile",
                               0,
                               "",
                               "",
                               "",
                               "",
                               "Makefile name:" filename);
      if (promptResult == "") {
         // user cancelled makefile generation
         return 0;
      }

      filename = _param1;
      if(filename == "") {
         filename = MAKEFILE_DEFAULT_FILE;
      }

      filename = _parse_project_command(filename, "", projectName, "");

      // get the filename absolute to the project dir
      filename = _AbsoluteToProject(filename, projectName);

      // note that the makefile is being generated
      mou_hour_glass(1);
      //message(MAKEFILE_GENERATION_MSG);
   }

   _str makefileGenMsg = stranslate(MAKEFILE_GENERATION_MSG, strip_filename(projectName, "P"), "%s1");
   message(makefileGenMsg);

   // expand the Files node to make sure wildcards are expanded
   _ExpandXMLFilesNode(projectHandle, _ProjectGet_FilesNode(projectHandle));

   // if there are both gnuc and vcpp makefiles to generate, the makefile name must be modified
   // so that both files can be generated
   if(gnucConfigList._length() > 0 && vcppConfigList._length() > 0) {
      // this do while(false) loop is here to allow breaking out of this code early without
      // using a return statement (so it will still go thru the cleanup code at the end)
      do {
         // generate gnuc makefile with the gnuc configurations
         _str gnucMakefileName = strip_filename(filename,'E') "-gnu" get_extension(filename, true);
         status = generate_makefile_type(projectHandle, gnucMakefileName, MAKEFILE_FORMAT_GNUMAKE,
                                         gnucConfigList, openAfterGen, replaceEnvVars, quiet);
         if(status) break;

         // generate vcpp makefile with the vcpp configurations
         _str vcppMakefileName = strip_filename(filename,'E') "-vcpp" get_extension(filename, true);
         status = generate_makefile_type(projectHandle, vcppMakefileName, MAKEFILE_FORMAT_NMAKE,
                                         vcppConfigList, openAfterGen, replaceEnvVars, quiet);
      } while(false);

   } else if(gnucConfigList._length() > 0) {
      // generate the makefile
      status = generate_makefile_type(projectHandle, filename, MAKEFILE_FORMAT_GNUMAKE,
                                      gnucConfigList, openAfterGen, replaceEnvVars, quiet);

   } else if(vcppConfigList._length() > 0) {
      // generate the makefile
      status = generate_makefile_type(projectHandle, filename, MAKEFILE_FORMAT_NMAKE,
                                      vcppConfigList, openAfterGen, replaceEnvVars, quiet);
   }

   // clear the generating makefile message
   if(status) {
      message("Makefile generation failed (" status ")");
   } else {
      clear_message();
   }
   mou_hour_glass(0);

   // close the project file
   _xmlcfg_close(projectHandle);

   return status;
}

/**
 */
static int generate_makefile_type(int projectHandle, _str makefileName, int makefileFormat,
                                  _str (&configList)[], boolean openAfterGen,
                                  boolean replaceEnvVars, boolean quiet)
{
   int status = 0;
   boolean warnAboutPaths = false;
   _str projectName = _xmlcfg_get_filename(projectHandle);

   // check project name for spaces
   if(pos(" ", projectName)) {
      warnAboutPaths = true;
   }

   // open the file
   int tempViewID = 0;
   int origViewID = 0;
   status = _open_temp_view(makefileName, tempViewID, origViewID);
   if(status) {
      // if the file was not found, create it
      if(status == FILE_NOT_FOUND_RC) {
         origViewID = _create_temp_view(tempViewID);
         if(origViewID == "") {
            return INSUFFICIENT_MEMORY_RC;
         }
      } else {
         return status;
      }
   }

   // scan for user changes that should be preserved
   _str userMod = "";
   top();
   status = search("^"_escape_re_chars(MAKEFILE_USERMOD_BEGIN) "$","@rh");
   if(status == 0) {
      // step to next line
      down();
      long userModStart = _QROffset();

      status = search("^" _escape_re_chars(MAKEFILE_USERMOD_END) "$", "@rh");
      if(status == 0) {
         long userModEnd = _QROffset();
         long userModLength = userModEnd - userModStart;
         if(userModLength > 0) {
            userMod = get_text((int)userModLength, (int)userModStart);
         }
      }
   }

   // now that the users modifications have been saved, clear the file and start over
   p_window_id = origViewID;
   _delete_temp_view(tempViewID);
   tempViewID = 0;

   origViewID = _create_temp_view(tempViewID);
   if(origViewID == "") {
      // clear the generating makefile message
      clear_message();
      mou_hour_glass(0);
      return INSUFFICIENT_MEMORY_RC;
   }
   p_buf_name = makefileName;
   p_UTF8 = _load_option_UTF8(p_buf_name);

   // add the banner to the top of the file
   insert_line(MAKEFILE_BANNER);

   // set the make command that will be used to make all dependencies
   insert_line("");
   insert_line("# Make command to use for dependencies");
   switch(makefileFormat) {
      case MAKEFILE_FORMAT_GNUMAKE:
         insert_line("MAKE="  _findGNUMake());
         insert_line("RM=rm");
         insert_line("MKDIR=mkdir");
         break;

      case MAKEFILE_FORMAT_NMAKE:
         insert_line("MAKE=nmake");
         insert_line("RM=del");
         insert_line("MKDIR=mkdir");
         break;
   }
   insert_line("");

   // add the users modifications
   insert_line("all: $(OUTFILE)"); //LEO
   insert_line("DEPENDENCIES=@set -e; rm -f $@;$(patsubst \"$(@:.d=.o)\",\"$@.$$$$\",$(COMPILE)) -MM -MT $(@:.d=.o); sed 's,\\($*\\)\\.o[ :]*,\\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$"); // LEO
   insert_line(MAKEFILE_USERMOD_BEGIN);

   if(length(userMod) > 0) {
      // userMod is exactly as it was entered by the user, including
      // any line endings.  therefore, a blank line is entered to move
      // to the next line and then the data is entered.  this always
      // results in an extra blank line so it is removed from the end
      insert_line("");
      _insert_text(userMod);
      _insert_text(MAKEFILE_USERMOD_END);
      //down();
      //delete_line();
   } else {
      // leave one blank line for the user to begin editing
      insert_line("");
      insert_line(MAKEFILE_USERMOD_END);
   }
   //insert_line(MAKEFILE_USERMOD_END);

   // this will be populated with relative paths to source files.  the list will then
   // be used to construct build rules that cover source files in each folder
   _str sourcedirHash:[];

   // array of all compile commands for a configuration (includes extension-specific commands)
   // NOTE: this array is used to build the list of commands (COMPILE, COMPILE_RULE1, etc), but
   //       is not used in generation of pattern rules.  pattern rules are generated as needed
   //       based on the compileToolInputHash table
   COMPILE_TOOL_INFO compileToolArray[] = null;

   // hash of compile tools for quick lookup when building pattern rules later
   COMPILE_TOOL_INFO compileToolInputHash:[] = null;
   boolean compileToolOutputHash:[] = null;

   // remember which configuration was active
   _str activeConfig = getActiveProjectConfig(projectName);

   // NOTE: if there is only one configuration, do not use any conditionals when
   // constructing the makefile and do not specify a default config
   if(configList._length() > 1) {
      // setup default configuration if none is specified
      insert_line("");
      insert_line("# If no configuration is specified, \"" configList[0] "\" will be used");
      switch(makefileFormat) {
         case MAKEFILE_FORMAT_GNUMAKE:
            insert_line("ifndef CFG");
            insert_line("CFG=" configList[0]);
            insert_line("endif");
            break;

         case MAKEFILE_FORMAT_NMAKE:
            insert_line("!if \"$(CFG)\" == \"\"");
            insert_line("CFG=" configList[0]);
            insert_line("!endif");
            break;
      }
   }

   // for each configuration, setup its variables
   int i = 0;
   for(i = 0; i < configList._length(); i++) {
      _str configName = configList[i];



      // find config node
      int configNode = _ProjectGet_ConfigNode(projectHandle, configName);
      if(configNode < 0) continue;

      // check configuration name for spaces
      if(pos(" ", configName)) {
         warnAboutPaths = true;
      }

      // clear compile tool information
      compileToolArray._makeempty();
      compileToolInputHash._makeempty();
      compileToolOutputHash._makeempty();

      // make the config active
      // NOTE: at this time, project_config_set_active causes all projects this one is
      //       dependant on to also change configs (which can be quite slow).  it would
      //       be better to make project_config_set_active take a recursive boolean that
      //       defaults to true
      //say("Change config: " configName);
      project_config_set_active(configName, projectName, true);

      // restore temp view id after project_config_set_active because it may have been changed
      p_window_id = tempViewID;

      // add configuration label and conditional
      insert_line("");
      insert_line("#");
      insert_line("# Configuration: " configName);
      insert_line("#");
      if(configList._length() > 1) {
         switch(makefileFormat) {
            case MAKEFILE_FORMAT_GNUMAKE:
               insert_line("ifeq \"$(CFG)\" \"" configName "\"");
               break;

            case MAKEFILE_FORMAT_NMAKE:
               insert_line("!if \"$(CFG)\" == \"" configName "\"");
               break;
         }
      }

      // parse primary compile tool
      int compileTargetNode = _ProjectGet_TargetNode(projectHandle, "compile", configName);
      _str compileToolLine = "";
      if(compileTargetNode >= 0) {
         _str cmdLine = _ProjectGet_TargetCmdLine(projectHandle, compileTargetNode);
         _str otherOpts = _ProjectGet_TargetOtherOptions(projectHandle, compileTargetNode);

         // build and save the compile command for insertion into the makefile later
         cmdLine = prepareCommand(projectName, makefileFormat, cmdLine, "compile", "", otherOpts, replaceEnvVars);
         compileToolLine = "COMPILE=" makeCommandCLSafe(cmdLine);

         //// save information about this compile tool for use building rules
         //COMPILE_TOOL_INFO compileToolInfo;
         //compileToolInfo.cmdLine = cmdLine;
         //compileToolInfo.variableName = "COMPILE";
         //compileToolInfo.inputExt = "";
         //compileToolInfo.outputExt = _ProjectGet_TargetOutputExts(projectHandle, compileTargetNode);
         //compileToolInfo.linkObject = true;
         //compileToolInfo.chainedOutputExt = "";
         //compileToolInfo.chainedLinkObject = true;
         //
         //// remove the wildcard
         //if(first_char(compileToolInfo.outputExt) == '*') {
         //   compileToolInfo.outputExt = substr(compileToolInfo.outputExt, 2);
         //}
         //
         //if(compileToolInfo.outputExt == "") {
         //   // use default
         //   switch(makefileFormat) {
         //      case MAKEFILE_FORMAT_GNUMAKE:
         //         compileToolInfo.outputExt = ".o";
         //         break;
         //
         //      case MAKEFILE_FORMAT_NMAKE:
         //         compileToolInfo.outputExt = ".obj";
         //         break;
         //   }
         //}
         //
         //// add it to the compile tool array for *.c files
         //compileToolInfo.inputExt = ".c";
         //compileToolArray[compileToolArray._length()] = compileToolInfo;
         //
         //// add it to the compile tool array for *.cpp files
         //compileToolInfo.inputExt = ".cpp";
         //compileToolArray[compileToolArray._length()] = compileToolInfo;
      }

      // parse compile rules
      int rulesList[] = null;
      _ProjectGet_Rules(projectHandle, rulesList, "compile", configName);
      int k = 0;
      for(k = 0; k < rulesList._length(); k++) {
         int ruleNode = rulesList[k];
         if(ruleNode < 0) continue;

         _str cmdLine = _ProjectGet_TargetCmdLine(projectHandle, ruleNode);
         _str otherOpts = _ProjectGet_TargetOtherOptions(projectHandle, ruleNode);

         // get list of input extensions
         _str extensionArray[] = null;
         _str extensionList = _ProjectGet_TargetInputExts(projectHandle, ruleNode);
         for(;;) {
            _str curExtension = "";
            parse extensionList with curExtension ";" extensionList;

            // remove the wildcard
            if(first_char(curExtension) == '*') {
               curExtension = substr(curExtension, 2);
            }
            if(curExtension == "") break;
            //say("curExtension: " curExtension);

            // add this extension to the array
            extensionArray[extensionArray._length()] = strip(curExtension);
         }

         // build the rule name
         _str ruleName = "COMPILE";
         if(extensionArray._length() > 0) {
            ruleName = ruleName extensionArray[0];
         }
         ruleName = stranslate(ruleName, '_', '.');
         ruleName = upcase(ruleName);
         //say("ruleName: " ruleName);

         // create dummy filename with the first extension so that _parse_project_command
         // pulls info from the proper line in the project
         _str dummyFilename = "";
         if(extensionArray._length() >= 1) {
            dummyFilename = "dummyFilename" extensionArray[0];
         }

         cmdLine = prepareCommand(projectName, makefileFormat, cmdLine, "compile", dummyFilename, otherOpts, replaceEnvVars);

         // save information about this compile tool for use building rules
         int m = 0;
         for(m = 0; m < extensionArray._length(); m++) {
            COMPILE_TOOL_INFO compileToolInfo;
            compileToolInfo.cmdLine = cmdLine;
            compileToolInfo.variableName = ruleName;
            compileToolInfo.inputExt = extensionArray[m];
            compileToolInfo.outputExt = _ProjectGet_TargetOutputExts(projectHandle, ruleNode);
            compileToolInfo.linkObject = (_ProjectGet_TargetLinkObject(projectHandle, ruleNode) == "0") ? false : true;
            compileToolInfo.chainedOutputExt = "";
            compileToolInfo.chainedLinkObject = true;
            compileToolInfo.ignore = false;

            // remove the wildcard
            if(first_char(compileToolInfo.outputExt) == '*') {
               compileToolInfo.outputExt = substr(compileToolInfo.outputExt, 2);
            }

            if(compileToolInfo.outputExt == "") {
               // use default
               switch(makefileFormat) {
                  case MAKEFILE_FORMAT_GNUMAKE:
                     compileToolInfo.outputExt = ".o";
                     break;

                  case MAKEFILE_FORMAT_NMAKE:
                     compileToolInfo.outputExt = ".obj";
                     break;
               }
            }

            // add it to the compile tool array
            compileToolArray[compileToolArray._length()] = compileToolInfo;

            // add it to the compile tool hash table by the input extension
            compileToolInputHash:[compileToolInfo.inputExt] = compileToolInfo;

            // flag the output extension as generated by at least one rule
            compileToolOutputHash:[compileToolInfo.outputExt] = true;
         }
      }

      // build object list of files that are common to all configurations
      _str commonObjs = "";
      _str nlCommonObjs = "";
      getObjectList(projectHandle, makefileFormat, sourcedirHash, compileToolInputHash, configName, commonObjs, nlCommonObjs, warnAboutPaths, true);

      // check other strings for variables that should be expanded
      _str outputFile = prepareCommand(projectName, makefileFormat, _ProjectGet_OutputFile(projectHandle, configName), "outfile", "", "", replaceEnvVars);
      _str includes = prepareCommand(projectName, makefileFormat, "%ir", "includes", "", "", replaceEnvVars);
      _str libs = prepareCommand(projectName, makefileFormat, "%libs", "libs", "", "", replaceEnvVars);
      // add any object files that are listed as part of the project directly
      append_object_files(libs,projectHandle,configName,makefileFormat);

      _str cfgObjs = "";
      _str nlCfgObjs = "";
      getObjectList(projectHandle, makefileFormat, sourcedirHash, compileToolInputHash, configName, cfgObjs, nlCfgObjs, warnAboutPaths);

      // determine whether non-linkable object support is needed
      boolean hasNLObjects = (nlCommonObjs != "" || nlCfgObjs != "");

      _str fullObjectList = _generate_objs_list(cfgObjs' 'commonObjs,libs,_ProjectGet_PreObjectLibs(projectHandle, configName));

      // if no outdir is specified, default to a dir of the same name as the configuration
      // and make sure it ends with a filesep
      _str outdir = prepareCommand(projectName, makefileFormat, "%bd", "outdir", "", "", replaceEnvVars);
      if(outdir == "") {
         outdir = configName;
      }

      // make outdir relative to project
      outdir = _RelativeToProject(outdir, projectName);
      outdir = strip(outdir, "T", FILESEP);
      outdir = strip(outdir, "T", FILESEP2);

      // fix fileseps on variables that contain files
      outputFile = fixFileSeps(outputFile, makefileFormat);
      includes = fixFileSeps(includes, makefileFormat);
      libs = fixFileSeps(libs, makefileFormat);
      fullObjectList = fixFileSeps(fullObjectList, makefileFormat);
      outdir = fixFileSeps(outdir, makefileFormat);

      // check outdir for spaces
      if(pos(" ", outdir)) {
         warnAboutPaths = true;
      }

      // add commands
      insert_line("OUTDIR=" outdir);
      insert_line("OUTFILE=" makeCommandCLSafe(outputFile));
      splitAndInsertValueList("CFG_INC", makeCommandCLSafe(includes));
      splitAndInsertValueList("CFG_LIB", makeCommandCLSafe(libs));
      splitAndInsertValueList("CFG_OBJ", makeCommandCLSafe(cfgObjs));
      splitAndInsertValueList("COMMON_OBJ", makeCommandCLSafe(commonObjs));
      insert_line("OBJ=$(COMMON_OBJ) $(CFG_OBJ)");
      splitAndInsertValueList("ALL_OBJ", makeCommandCLSafe(fullObjectList));

      // only add these if there are non-linkable objects
      if(hasNLObjects) {
         splitAndInsertValueList("CFG_OBJ_NL", makeCommandCLSafe(nlCfgObjs));
         splitAndInsertValueList("COMMON_OBJ_NL", makeCommandCLSafe(nlCommonObjs));
         insert_line("OBJ_NL=$(COMMON_OBJ_NL) $(CFG_OBJ_NL)");
      }

      // get the commands and modify them for use in the makefile
      insert_line("");

      // add compile target from <Menu> container (this was parsed earlier)
      insert_line(compileToolLine);

      // find the link command
      int linkTargetNode = _ProjectGet_TargetNode(projectHandle, "link", configName);
      if(linkTargetNode >= 0) {
         _str cmdLine = _ProjectGet_TargetCmdLine(projectHandle, linkTargetNode);
         _str otherOpts = _ProjectGet_TargetOtherOptions(projectHandle, linkTargetNode);

         cmdLine = prepareCommand(projectName, makefileFormat, cmdLine, "link", "", otherOpts, replaceEnvVars);
         insert_line("LINK=" makeCommandCLSafe(cmdLine));
      }

      // add rules from <Rules> container (these were parsed earlier)
      int r;
      for(r = 0; r < compileToolArray._length(); r++) {
         _str ruleName = compileToolArray[r].variableName;

         // if this is the default COMPILE rule, skip it because it was already added
         if(strieq(ruleName, "COMPILE")) continue;

         // if this is the same name as the previous rule, skip it
         if(r > 0 && strieq(ruleName, compileToolArray[r - 1].variableName)) continue;

         insert_line(compileToolArray[r].variableName "=" makeCommandCLSafe(compileToolArray[r].cmdLine));
      }

      // determine which dependency set to use.  'build' is the default if not specified
      int buildTargetNode = _ProjectGet_TargetNode(projectHandle, "build", configName);
      _str dependsRef = _ProjectGet_TargetDependsRef(projectHandle, buildTargetNode);
      if(dependsRef == "") {
         dependsRef = "build";
      }

      // get list of dependency nodes for this configuration
      int dependencyNodeArray[] = null;
      _ProjectGet_DependencyProjectNodesForRef(projectHandle, configName, dependsRef, dependencyNodeArray);
      boolean hasDependencies = (dependencyNodeArray._length() > 0);

      //int d;
      //for(d = 0; d < dependencyNodeArray._length(); d++) {
      //   say("dep(" d "): " _xmlcfg_get_attribute(projectHandle, dependencyNodeArray[d], "Project") \
      //       "  -  " _xmlcfg_get_attribute(projectHandle, dependencyNodeArray[d], "Config"));
      //}

      // add pattern rules
      insert_line("");
      if(!sourcedirHash._isempty()) {
         insert_line("# Pattern rules");

         typeless o;
         for(o._makeempty();;) {
            compileToolInputHash._nextel(o);
            if(o._isempty()) break;
            //say("o="o "   in: " compileToolInputHash:[o].inputExt "   out: " compileToolInputHash:[o].outputExt);

            // if this extension has ignore set to true, skip it
            if(compileToolInputHash:[o].ignore) {
               continue;
            }

            typeless n;
            for (n._makeempty();;) {
               sourcedirHash._nextel(n);
               if (n._isempty()) break;
               //say("n: " n);

               // make sure fileseps are proper
               _str sourceDir = fixFileSeps(n, makefileFormat);

               switch(makefileFormat) {
                  case MAKEFILE_FORMAT_GNUMAKE:
                     insert_line("$(OUTDIR)" getFileSep(makefileFormat) "%" compileToolInputHash:[o].outputExt " : " sourceDir "%" compileToolInputHash:[o].inputExt);
                     break;

                  case MAKEFILE_FORMAT_NMAKE:
                     insert_line("{" sourceDir "}" compileToolInputHash:[o].inputExt "{$(OUTDIR)}" compileToolInputHash:[o].outputExt " :");
                     break;
               }
               insert_line("\t$(" compileToolInputHash:[o].variableName ")");
               insert_line("");

               if (makefileFormat == MAKEFILE_FORMAT_GNUMAKE) {                                                                      // LEO
                  insert_line("$(OUTDIR)" getFileSep(makefileFormat) "%.d : " sourceDir "%" compileToolInputHash:[o].inputExt);      // LEO
                  insert_line("\t$(DEPENDENCIES)");                                                                                  // LEO
                  insert_line("");                                                                                                   // LEO
               }


            }

            // check to see if the input to this rule couldve come from another rule
            // NOTE: this is done by checking compileToolOutputHash to see if the input
            //       ext to this rule can be generated by another rule
            COMPILE_TOOL_INFO* chainedToolInfoPtr = compileToolOutputHash._indexin(compileToolInputHash:[o].inputExt);
            if(chainedToolInfoPtr) {
               // input to this rule might be chained so must add pattern
               //    $(OUTDIR)/%.outputext : $(OUTDIR)/%.inputext
               // to handle output from previous compiles that is in outdir
               switch(makefileFormat) {
                  case MAKEFILE_FORMAT_GNUMAKE:
                     insert_line("$(OUTDIR)" getFileSep(makefileFormat) "%" compileToolInputHash:[o].outputExt " : $(OUTDIR)" getFileSep(makefileFormat) "%" compileToolInputHash:[o].inputExt);
                     break;

                  case MAKEFILE_FORMAT_NMAKE:
                     insert_line("{$(OUTDIR)}" compileToolInputHash:[o].inputExt "{$(OUTDIR)}" compileToolInputHash:[o].outputExt " :");
                     break;
               }
               insert_line("\t$(" compileToolInputHash:[o].variableName ")");
               insert_line("");
            }
         }
      }

      // check for pre and post build commands
      _str preBuildCmds = _ProjectGet_PreBuildCommandsList(projectHandle, configName);
      _str postBuildCmds = _ProjectGet_PostBuildCommandsList(projectHandle, configName);

      // add the compiling rules
      insert_line("# Build rules");

      // add standard 'all' rule
      _str allRule = "all:";
      if(preBuildCmds != "") {
         allRule = allRule " prebuildcmds";
      }
      allRule = allRule " $(OUTFILE)";
      insert_line(allRule);
      insert_line("");

      // add pre build commands
      if(preBuildCmds != "") {
         insert_line("prebuildcmds:");
         insertCommandList(projectName, preBuildCmds, makefileFormat, "prebuild",
                           strieq(_ProjectGet_StopOnPreBuildError(projectHandle, configName), "1"));
         insert_line("");
      }

      // add output file rule (modifying the rule as appropriate if dependencies are present)
      _str depsString = "";
      if(hasDependencies) {
         depsString = depsString "deps";
      }
      if(hasNLObjects) {
         depsString = depsString " $(OBJ_NL)";
      }
      insert_line("$(OUTFILE): $(OUTDIR) " depsString " $(OBJ)");

      //insert_line("\t@echo \"Building config: $(CFG)\"");
      insert_line("\t$(LINK)");

      // add post build commands under the link command so that they will only be
      // run if a link is required
      if(postBuildCmds != "") {
         insertCommandList(projectName, postBuildCmds, makefileFormat, "postbuild",
                           strieq(_ProjectGet_StopOnPostBuildError(projectHandle, configName), "1"));
      }

      // add outdir rule
      insert_line("");
      insert_line("$(OUTDIR):");
      switch(makefileFormat) {
         case MAKEFILE_FORMAT_GNUMAKE:
            insert_line("\t$(MKDIR) -p \"$(OUTDIR)\"");
            break;

         case MAKEFILE_FORMAT_NMAKE:
            insert_line("\tif not exist \"$(OUTDIR)" getFileSep(makefileFormat) "$(NULL)\" $(MKDIR) \"$(OUTDIR)\"");
            break;
      }

      // if there are dependencies, setup a rule to build them
      if(hasDependencies) {
         insert_line("");
         insert_line("# Build dependencies");
         insert_line("deps:");

         // each dep command should be of the form (cd ../sub1/sub2;$(MAKE) -f blah.mak CFG=$(CFG))
         // NOTE: these commands are within parens to force them to be shelled on UNIX, therefore
         //       eliminating the need to change back to the current dir at the end of the build
         int p = 0;
         for(p = 0; p < dependencyNodeArray._length(); p++) {
            int dependencyNode = dependencyNodeArray[p];
            if(dependencyNode < 0) continue;

            _str dependencyProject = _xmlcfg_get_attribute(projectHandle, dependencyNode, "Project");
            _str dependencyConfig = _xmlcfg_get_attribute(projectHandle, dependencyNode, "Config", "$(CFG)");
            if(dependencyProject == "") continue;
            dependencyProject = fixFileSeps(dependencyProject, makefileFormat);
            //say("dep: " dependencyProject "  depConfig: " dependencyConfig);

            // get the directory where the project lives as well as the makefile for the project
            _str dependencyDir = strip_filename(dependencyProject, "N");
            if(dependencyDir == "") {
               dependencyDir = ".";
            }
            //say("depDir: " dependencyDir);

            // resolve symlinks if on UNIX
            if(__UNIX__ && def_resolve_dependency_symlinks) {
               // absolute the directory to the project
               dependencyDir = _AbsoluteToProject(dependencyDir);

               // try to resolve symlinks in the project path
               _str resolvedProject = _FileReadLink(_project_name);
               if(resolvedProject != "") {
                  dependencyDir = _RelativeToProject(dependencyDir, resolvedProject);
               }
               if(dependencyDir == "") {
                  dependencyDir = ".";
               }
               //say("resolvedDepDir: " dependencyDir);
            }

            // get the dependency makefile name from the dependency project
            _str absoluteDependency = _AbsoluteToProject(dependencyProject, projectName);
            int dependencyHandle = _ProjectHandle(absoluteDependency);
            if(dependencyHandle < 0) continue;
            _str dependencyMakefile = _ProjectGet_BuildMakeFile(dependencyHandle);

            // make sure that dependencyMakefile is valid
            if(dependencyMakefile == "") {
               dependencyMakefile = strip_filename(dependencyProject, "PE") MAKEFILE_EXTENSION;
            } else {
               dependencyMakefile = _parse_project_command(dependencyMakefile, '', absoluteDependency,"");
               dependencyMakefile = _RelativeToProject(dependencyMakefile, absoluteDependency);
            }

            // add commands to change to this directory and build this dependency
            switch(makefileFormat) {
               case MAKEFILE_FORMAT_GNUMAKE:
                  insert_line("\t@(cd " dependencyDir ";$(MAKE) -f " dependencyMakefile " CFG=" dependencyConfig ")");
                  break;

               case MAKEFILE_FORMAT_NMAKE:
                  _str dependencyDrive = "";
                  if(substr(dependencyDir, 2, 1) == ":") {
                     // the dependency dir is an absolute filename which means it is most likely on
                     // a different drive than the current project, so add the drive: to the command
                     dependencyDrive = substr(dependencyDir, 1, 2);

                     // add the & so the drive change command can be added into the string of commands
                     // to be executed
                     dependencyDrive = dependencyDrive " & ";
                  }

                  //say("depDir: \"" dependencyDir "\" cwd=\"" getcwd() "\" drv=\"" dependencyDrive "\"");
                  insert_line("\t@(" dependencyDrive "cd " dependencyDir " & $(MAKE) -f " dependencyMakefile " CFG=" dependencyConfig ")");
                  break;
            }
         }
      }

      // add rebuild rule
      insert_line("");
      insert_line("# Rebuild this project");
      insert_line("rebuild: cleanall all");

      // add cleaning rules
      insert_line("");
      insert_line("# Clean this project");
      insert_line("clean:");
      switch(makefileFormat) {
         case MAKEFILE_FORMAT_GNUMAKE:
            insert_line("\t$(RM) -f $(OUTFILE)");
            insert_line("\t$(RM) -f $(OBJ)");
            if(hasNLObjects) {
               insert_line("\t$(RM) -f $(OBJ_NL)");
            }
            break;

         case MAKEFILE_FORMAT_NMAKE:
            insert_line("\t$(RM) /q $(OUTFILE)");
            insert_line("\t$(RM) /q $(OBJ)");
            if(hasNLObjects) {
               insert_line("\t$(RM) /q $(OBJ_NL)");
            }
            break;
      }

      insert_line("");
      insert_line("# Clean this project and all dependencies");
      insert_line("cleanall: clean");
      if(hasDependencies) {
         // each dep command should be of the form (cd ../sub1/sub2;$(MAKE) -f blah.mak clean CFG=$(CFG))
         // NOTE: these commands are within parens to force them to be shelled, therefore
         //       eliminating the need to change back to the current dir at the end of the build
         int q = 0;
         for(q = 0; q < dependencyNodeArray._length(); q++) {
            int dependencyNode = dependencyNodeArray[q];
            if(dependencyNode < 0) continue;

            _str dependencyProject = _xmlcfg_get_attribute(projectHandle, dependencyNode, "Project");
            _str dependencyConfig = _xmlcfg_get_attribute(projectHandle, dependencyNode, "Config", "$(CFG)");
            if(dependencyProject == "") continue;
            dependencyProject = fixFileSeps(dependencyProject, makefileFormat);

            // get the directory where the project lives as well as the makefile for the project
            _str dependencyDir = strip_filename(dependencyProject, "N");
            if(dependencyDir == "") {
               dependencyDir = ".";
            }

            // resolve symlinks if on UNIX
            if(__UNIX__ && def_resolve_dependency_symlinks) {
               // absolute the directory to the project
               dependencyDir = _AbsoluteToProject(dependencyDir);

               // try to resolve symlinks in the project path
               _str resolvedProject = _FileReadLink(_project_name);
               if(resolvedProject != "") {
                  dependencyDir = _RelativeToProject(dependencyDir, resolvedProject);
               }
               if(dependencyDir == "") {
                  dependencyDir = ".";
               }
            }

            // get the dependency makefile name from the dependency project
            _str absoluteDependency = _AbsoluteToProject(dependencyProject, projectName);
            int dependencyHandle = _ProjectHandle(absoluteDependency);
            if(dependencyHandle < 0) continue;
            _str dependencyMakefile = _ProjectGet_BuildMakeFile(dependencyHandle);

            // make sure that dependencyMakefile is valid
            if(dependencyMakefile == "") {
               dependencyMakefile = strip_filename(dependencyProject, "PE") MAKEFILE_EXTENSION;
            } else {
               dependencyMakefile = _parse_project_command(dependencyMakefile, '', absoluteDependency,"");
               dependencyMakefile = _RelativeToProject(dependencyMakefile, absoluteDependency);
            }

            // add commands to change to this directory and clean it
            switch(makefileFormat) {
               case MAKEFILE_FORMAT_GNUMAKE:
                  insert_line("\t@(cd " dependencyDir ";$(MAKE) -f " dependencyMakefile " cleanall CFG=" dependencyConfig ")");
                  break;

               case MAKEFILE_FORMAT_NMAKE:
                  _str dependencyDrive = "";
                  if(substr(dependencyDir, 2, 1) == ":") {
                     // the dependency dir is an absolute filename which means it is most likely on
                     // a different drive than the current project, so add the drive: to the command
                     dependencyDrive = substr(dependencyDir, 1, 2);

                     // add the & so the drive change command can be added into the string of commands
                     // to be executed
                     dependencyDrive = dependencyDrive " & ";
                  }

                  insert_line("\t@(" dependencyDrive "cd " dependencyDir " & $(MAKE) -f " dependencyMakefile " cleanall CFG=" dependencyConfig ")");
                  break;
            }
         }
      }



      // close conditional
      if(configList._length() > 1) {
         switch(makefileFormat) {
            case MAKEFILE_FORMAT_GNUMAKE:
               insert_line("endif");
               insert_line("include $(COMMON_OBJ:.o=.d)"); //LEO
               break;

            case MAKEFILE_FORMAT_NMAKE:
               insert_line("!endif");
               break;
         }
      }
   }

   // restore original active config
   project_config_set_active(activeConfig, projectName, true);

   // save the makefile
   status = _save_file(build_save_options(p_buf_name) " -E");

   // clean up the temp view
   p_window_id = origViewID;
   _delete_temp_view(tempViewID);

   // open the makefile
   if(openAfterGen) {
      edit(maybe_quote_filename(makefileName));
   }

   // restore the original view since the edit command will trash it
   p_window_id = origViewID;

   // warn that paths with spaces were encountered if this is a GNU makefile
   if(warnAboutPaths && !quiet) {
      switch(makefileFormat) {
         case MAKEFILE_FORMAT_GNUMAKE:
            _message_box("Some paths or configuration names in this makefile contain spaces which may cause problems during a build.");
            break;

         case MAKEFILE_FORMAT_NMAKE:
         default:
            break;
      }
   }

   return status;
}

/**
 * Prepare a command for use in a makefile.  This will replace
 * all %variables with its counterpart in the makefile.
 *
 * @param command  Command to be prepared
 *
 * @return The prepared command
 */
static _str prepareCommand(_str projectName, int makefileFormat, _str command, _str which,
                           _str compileExt, _str otherOpts, boolean replaceEnvVars)
{
   _str preparedCmd = command;

   // replace all environment variables %(varname)
   if(replaceEnvVars) {
      preparedCmd = _parse_env_vars(preparedCmd);
   } else {
      preparedCmd = reformatEnvVars(preparedCmd);
   }

   // replace %variables that will have corresponding variables in the
   // makefile.  order of the replacement is important.  the replacements
   // may be different for compile and link commands.
   //    compile:
   //       %bd -> $(OUTDIR) FILESEP
   //       %ir -> $(INC)
   //       %i  -> $(INC)
   //       %f  -> $<
   //       %n  -> $*
   //    link:
   //       %bd -> $(OUTDIR) FILESEP
   //       %ir -> $(INC)
   //       %i  -> $(INC)
   //       %f  -> $(OBJ)
   //       %n  ->
   //    outdir:
   //       %bd *MUST* pass thru to be determined properly
   //say("prepare(" which "): " preparedCmd);
   switch(which) {
      case "compile":
         //preparedCmd = stranslate(preparedCmd, "$*", "%bd%n", "I");
         preparedCmd = stranslate(preparedCmd, "$(OUTDIR)" getFileSep(makefileFormat), "%bd", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_INC)", "%ir", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_INC)", "%i", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_LIB)", "%libs", "I");
         preparedCmd = stranslate(preparedCmd, "$(ALL_OBJ)", "%objs", "I");
         preparedCmd = stranslate(preparedCmd, "$<", "%f", "I");
         preparedCmd = stranslate(preparedCmd, "$(*F)", "%n", "I");
         preparedCmd = stranslate(preparedCmd, "$(*D)", "%p", "I");
         preparedCmd = stranslate(preparedCmd, otherOpts, "%~other", "I");

         // special cases
         preparedCmd = stranslate(preparedCmd, "$<", "@?\"?%[Ll][Ff]\"?", "U"); // [@]["]%lf["]
         preparedCmd = stranslate(preparedCmd, '$(OUTFILE)\\1', "%[Oo]([^npe])", "U"); // %o but not %on, %op, or %oe
         break;

      case "link":
         preparedCmd = stranslate(preparedCmd, "$(OUTDIR)" getFileSep(makefileFormat), "%bd", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_INC)", "%ir", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_INC)", "%i", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_LIB)", "%libs", "I");
         preparedCmd = stranslate(preparedCmd, "$(ALL_OBJ)", "%objs", "I");
         preparedCmd = stranslate(preparedCmd, "$(OBJ)", "%f", "I");
         preparedCmd = stranslate(preparedCmd, otherOpts, "%~other", "I");

         // special cases
         preparedCmd = stranslate(preparedCmd, "$(OBJ)", "@?\"?%[Ll][Ff]\"?", "U"); // [@]["]%lf["]
         preparedCmd = stranslate(preparedCmd, '$(OUTFILE)\\1', "%[Oo]([^npe])", "U"); // %o but not %on, %op, or %oe
         break;

      case "outdir":
      case "includes":
      case "libs":
         // this is here to force %bd, %i, %ir, %libs to be passed thru to _parse_project_command
         // and be resolved.  these are called when actually setting $(OUTDIR) $(OUTFILE)
         // $(CFG_INC) $(CFG_LIB)
         break;

      default:
         preparedCmd = stranslate(preparedCmd, "$(OUTDIR)" getFileSep(makefileFormat), "%bd", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_INC)", "%ir", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_INC)", "%i", "I");
         preparedCmd = stranslate(preparedCmd, "$(CFG_LIB)", "%libs", "I");
         preparedCmd = stranslate(preparedCmd, "$(ALL_OBJ)", "%objs", "I");

         // special cases
         preparedCmd = stranslate(preparedCmd, '$(OUTFILE)\\1', "%[Oo]([^npe])", "U"); // %o but not %on, %op, or %oe
         break;
   }

   // run parse_project_command to ensure there are no more %variables left
   preparedCmd = _parse_project_command(preparedCmd, compileExt, projectName, "");

   // convert all platform fileseps to the proper type expected by the make program
   //preparedCmd = fixFileSeps(preparedCmd, makefileFormat);

   //say("prepared: " preparedCmd);
   return preparedCmd;
}

/**
 * Change all envvars from %(ENVNAME) to $(ENVNAME) syntax
 *
 * @param command
 *
 * @return
 */
static _str reformatEnvVars(_str command)
{
   _str cmd = command;

   int offset = 1;
   for(;;) {
      offset = pos("%(\\(.+\\))", cmd, offset, "U");
      if(offset == 0) break;

      // replace the entry
      cmd = substr(cmd, 1, offset - 1) "$" substr(cmd, pos("S1"), pos("1")) :+ substr(cmd, offset + pos(""));
   }

   return cmd;
}

/**
 * Build a list of all objects used in the specified configuration.
 * If config is not specified, the objects common to all configs are
 * returned.
 *
 * @param projectInfo
 * @param config
 *
 * @return List of objects
 */
static int getObjectList(int projectHandle, int makefileFormat, _str (&sourcedirHash):[],
                         COMPILE_TOOL_INFO (&compileToolHash):[], _str configName,
                         _str& linkObjectList, _str& noLinkObjectList,
                         boolean& pathsContainedSpaces, boolean returnCommon = false)
{
   int status = 0;
   linkObjectList = "";
   noLinkObjectList = "";
   _str projectName = _xmlcfg_get_filename(projectHandle);

   typeless objectList[] = null;

   // if common files requested, build array of all files with no config attribute
   // or an empty config attribute
   if(returnCommon) {
      status = _xmlcfg_find_simple_array(projectHandle, "/Project/Files//F[not(@C)]", objectList);
      if(status) return status;
      status = _xmlcfg_find_simple_array(projectHandle, "/Project/Files//F[@C='']", objectList,
                                TREE_ROOT_INDEX, VSXMLCFG_FIND_APPEND);
      if(status) return status;
   } else {
      status = _xmlcfg_find_simple_array(projectHandle, "/Project/Files//F[contains(@C,'\"" configName "\"','I')]", objectList);
      if(status) return status;
   }

   // walk the list of files, building the object name
   int i = 0;
   for(i = 0; i < objectList._length(); i++) {
      _str line = _xmlcfg_get_attribute(projectHandle, objectList[i], "N");
      line = fixFileSeps(line, makefileFormat);
      //say("obj: " line);

      // translate the line in case the filename contained any placeholders
      // NOTE: only environment variables are likely here
      line = prepareCommand(projectName, makefileFormat, line, "filename", "", "", false);

      _str extension = get_extension(line, true);
      _str filename = strip_filename(line, "PE");
      _str relativePath = relative(strip_filename(line, "N"), strip_filename(projectName, "N"));

      // look for a compile rule that applies to the source file
      COMPILE_TOOL_INFO toolInfo;
      toolInfo.cmdLine = "";
      toolInfo.variableName = "";
      toolInfo.inputExt = extension;
      toolInfo.outputExt = "";
      toolInfo.linkObject = true;
      toolInfo.chainedOutputExt = "";
      toolInfo.chainedLinkObject = true;
      toolInfo.ignore = false;

      COMPILE_TOOL_INFO* storedToolInfoPtr = compileToolHash._indexin(extension);
      if(storedToolInfoPtr) {
         // copy the information from the pointer
         toolInfo = *storedToolInfoPtr;

         // check for ignored type
         if(toolInfo.ignore) {
            continue;
         }
      } else {
         // not found so check to see if objects with this extension are linkable
         _str copts = "";
         _str outputExt = "";
         boolean linkObject = true;
         int compileRuleTargetNode=0;

         // NOTE: this case is probably not used anymore, but it is being left for
         //       safety.  since compile rules are now parsed and entered into the
         //       compileToolHash before this function gets called, the if statement
         //       above should never fail when dealing with a file that will be
         //       compiled using a rule.  this leaves only files that will be compiled
         //       using the default rule to fall thru to this case, which will always
         //       cause this getExtSpecificCompileInfo() call to fail
         if(getExtSpecificCompileInfo(extension, projectHandle, configName, "", outputExt, compileRuleTargetNode, linkObject, false) == 0) {
            // remove the wildcard
            if(first_char(outputExt) == '*') {
               outputExt = substr(outputExt, 2);
            }

            // store the tool info in the hash table
            toolInfo.outputExt = outputExt;
            toolInfo.linkObject = linkObject;
            if(toolInfo.variableName == "") {
               // no variable name defined, so use the default
               toolInfo.variableName = "COMPILE";
            }
            compileToolHash:[extension] = toolInfo;

         } else {
            // if there is no extension specific compile tool, check to see if this file has
            // a standard source extension
            _str wildcardExtension = ";*" extension ";";
            _str sourceExtList = ";" SOURCE_EXTENSION_LIST ";";

            if(pos(wildcardExtension, sourceExtList)) {
               // if this point is reached, the default compile command will be used for this
               // file, so check it for output extension
               compileRuleTargetNode=0;
               getExtSpecificCompileInfo(extension, projectHandle, configName, "", outputExt, compileRuleTargetNode, linkObject, true);

               // remove the wildcard
               if(first_char(outputExt) == '*') {
                  outputExt = substr(outputExt, 2);
               }

               // if there is no output extension, use the default
               if(outputExt == "") {
                  switch(makefileFormat) {
                     case MAKEFILE_FORMAT_GNUMAKE:
                        outputExt = ".o";
                        break;

                     case MAKEFILE_FORMAT_NMAKE:
                        outputExt = ".obj";
                        break;
                  }
               }

               // store the tool info in the hash table
               toolInfo.outputExt = outputExt;
               toolInfo.linkObject = linkObject;
               if(toolInfo.variableName == "") {
                  // no variable name defined, so use the default
                  toolInfo.variableName = "COMPILE";
               }
               compileToolHash:[extension] = toolInfo;

            } else {
               // not a source file so store its extension in the table as ignored type
               toolInfo.outputExt = extension;
               toolInfo.ignore = true;
               compileToolHash:[extension] = toolInfo;
               continue;
            }
         }
      }

      // iterate until a compile rule is found that does not generate an extension
      // that is then chained into another rule
      //
      // NOTE: this is necessary because for make to properly chain rules, the
      //       object name needs to have the absolute final extension that it
      //       would have after all rules have been applied
      if(toolInfo.chainedOutputExt == "") {
         toolInfo.chainedOutputExt = toolInfo.outputExt;
         toolInfo.chainedLinkObject = toolInfo.linkObject;
      }
      for(;;) {
         _str chainedOutputExt = "";
         boolean chainedLinkObject = true;
         int chainedCompileRuleTargetNode = 0;
         if(getExtSpecificCompileInfo(toolInfo.chainedOutputExt, projectHandle, configName, "", chainedOutputExt, chainedCompileRuleTargetNode, chainedLinkObject, false) == 0) {
            // remove the wildcard
            if(first_char(chainedOutputExt) == '*') {
               chainedOutputExt = substr(chainedOutputExt, 2);
            }

            // make sure the rule is not chaining itself recursively
            if(strieq(toolInfo.chainedOutputExt, chainedOutputExt)) break;

            toolInfo.chainedOutputExt = chainedOutputExt;
            toolInfo.chainedLinkObject = chainedLinkObject;
            continue;
         }

         break;
      }

      // generate the object name for the file and add it to the appropriate object list
      // based on whether it is linkable or not
      filename = "$(OUTDIR)" getFileSep(makefileFormat) filename toolInfo.chainedOutputExt;
      if(toolInfo.chainedLinkObject) {
         // add it to the link object list
         if(linkObjectList == "") {
            linkObjectList = filename;
         } else {
            linkObjectList = linkObjectList " " filename;
         }
      } else {
         // add it to the nolink object list
         if(noLinkObjectList == "") {
            noLinkObjectList = filename;
         } else {
            noLinkObjectList = noLinkObjectList " " filename;
         }
      }

      // save the relative path for building compile rules
      sourcedirHash:[relativePath] = "1";

      // check for spaces
      if(pos(" ", relativePath) > 0) {
         pathsContainedSpaces = true;
      }
   }

   return status;
}

/**
 * Enter the value into the current view, splitting the list up
 * so that no value begins after column 70.  The values are
 * space delimited.
 *
 * <br>NOTE: A value will not be split across multiple lines so it
 *       may extend past column 70.
 *
 * @param name
 * @param valueList
 */
static void splitAndInsertValueList(_str name, _str valueList)
{
   _str line = name "=";
   boolean firstIteration = true;
   _str value = parseNextValue(valueList);
   while(value != "") {
      // if the line is over 70 chars (70 instead of 75 to allow room for expanded tab), insert
      // it with a line-continuation character
      if(length(line) + length(value) >= 70 && !firstIteration) {
         insert_line(line "\\");
         line = "\t";
      }

      // we need to monitor whether it is the first iteration or not to avoid outputting
      // NAME=\ on the first line if the first value is very long.  it should output
      // NAME=VALUE1 no matter how long the first value is
      firstIteration = false;

      // add the value to the line
      line = line value " ";

      // get next value
      value = parseNextValue(valueList);
   }

   // add the last line if there is anything there
   if(line != "") {
      insert_line(line);
   }
}

/**
 * Get filesep for the specified type of makefile
 *
 * @param makefileFormat
 *
 * @return Filesep for specified type of makefile
 */
static _str getFileSep(int makefileFormat)
{
   switch(makefileFormat) {
      case MAKEFILE_FORMAT_GNUMAKE:
         return MAKEFILE_FILESEP_GNUMAKE;

      case MAKEFILE_FORMAT_NMAKE:
         return MAKEFILE_FILESEP_NMAKE;
   }

   // default to just returning the platform specific filesep
   return FILESEP;
}

/**
 * Translate fileseps in the given line to the appropriate
 * ones for the specified platform
 *
 * @param line   Line to translate
 * @param makefileFormat
 *
 * @return Translated line
 */
static _str fixFileSeps(_str line, int makefileFormat)
{
   line = stranslate(line, getFileSep(makefileFormat), FILESEP);
   line = stranslate(line, getFileSep(makefileFormat), FILESEP2);
   return line;
}

/**
 * Return the first value in the line.  Quoted parameters are
 * supported.  This is very similar to parse_file.  The difference
 * is this supports quoted values that have other text prefixing
 * them.  One common example of this occurs when dealing with
 * compilers:
 *
 * -I"C:\Program Files\Subdir1" -I"C:\Program Files\Subdir2"
 *
 * @param line
 *
 * @return
 */
static _str parseNextValue(_str& line)
{
   _str option = "";
   line = strip(line, "B");
   int i = 1;
   for(i = 1; i <= length(line); i++) {
      _str ch = substr(line, i, 1);
      if(ch == " ") break;

      // check for quoted parameter
      if(ch == "\"") {
         int endQuote = pos(ch, line, i + 1);

         // if no endquote found, assume rest of line is quoted
         if(endQuote == 0) {
            endQuote = length(line);
         }

         option = option :+ substr(line, i, endQuote - i + 1);

         // move index past quoted value
         i = endQuote;
      } else {
         option = option :+ ch;
      }
   }

   // remove the option from the line
   line = strip(substr(line, i), "B");

   return option;
}

/**
 * Do a path search for 'gmake'.  Default to 'make' if not found.
 *
 * @return
 */
_str _findGNUMake()
{
   _str makeProgram = path_search("gmake");
   if(makeProgram != "") {
      return "gmake";
   }

   return "make";
}

/**
 * Parse and insert the commands from the ASCII 1 delimited list
 * into the current view
 *
 * @param projectName
 *               Project name
 * @param commandList
 *               ASCII 1 delimited list of commands
 * @param makefileFormat
 * @param which  Which set of commands is being processed (ex: prebuild, postbuild)
 * @param stopOnError
 *               Determines whether the commands are prefixed with a hyphen '-' to
 *               tell make to ignore their return code
 *
 * @return 0 on success, <0 otherwise
 */
static int insertCommandList(_str projectName, _str commandList, int makefileFormat, _str which,
                             boolean stopOnError)
{
   // make sure the command list ends with the delimiter
   // NOTE: this is done to make parsing easier by removing the border case
   //       that the last command presents
   _maybe_append(commandList, "\1");

   for(;;) {
      _str command = "";
      parse commandList with command "\1" commandList;
      if(command == "") break;

      // parse the command
      _str parsedCommand = prepareCommand(projectName, makefileFormat, command, which, "", "", false);

      // add the command to the makefile
      if(command != "") {
         if(stopOnError) {
            insert_line("\t" makeCommandCLSafe(parsedCommand));
         } else {
            insert_line("\t-" makeCommandCLSafe(parsedCommand));
         }
      }
   }

   return 0;
}

/**
 * Finds any object or library files that were added to the project file list and append
 * them to the library list.
 * 
 * @param libs the list of libraries to pass link
 * @param projectHandle the handle to the project file
 * @param configName the name of the active configuration
 * @param makefileFormat
 */
static void append_object_files(_str& libs, int projectHandle, _str configName, int makefileFormat)
{
   typeless objectList[] = null;

   int status = _xmlcfg_find_simple_array(projectHandle, "/Project/Files//F", objectList);
   if(status) return;

   int count;
   _str filename;
   _str extension;

   for(count=0;count<objectList._length();++count) {
      filename = _xmlcfg_get_attribute(projectHandle, objectList[count], "N");
      filename = fixFileSeps(filename, makefileFormat);
      extension = get_extension(filename);

      if (extension == "o" || extension == "obj" || extension == "lib" || extension == "a") {
         if (libs == "") {
            libs = filename;
         } else {
            libs = libs " " maybe_quote_filename(filename);
         }
      }
   }
}
